home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue46 / Uploader / CGIAPI.PAS next >
Encoding:
Pascal/Delphi Source File  |  1999-04-26  |  14.5 KB  |  491 lines

  1. {  **************************************************
  2.     Originally written by Steve Troxell for The Delphi
  3.     Magazine. Developing Dynamic Web Pages, Issue 16,
  4.     December 1996
  5.    **************************************************  }
  6. {  **************************************************
  7.     Modifyed by Paul Warren for The Delphi Magazine.
  8.     April 1999.
  9.    **************************************************  }
  10.  
  11. unit CGIAPI;
  12.  
  13. interface
  14.  
  15. uses SysUtils, Windows, Classes, INIFIles;
  16.  
  17. type
  18.   TEnvironmentType = (etStdCGI, etWinCGI);
  19.  
  20.   TCGI = class
  21.   private
  22.     FCGIItems: TStringList;
  23.     FFormItems: TStringList;
  24.     FEnvironmentType: TEnvironmentType;
  25.     FOutputFile: TextFile;
  26.     FWinCGIProfileName: string;
  27.     FWinCGIProfile: TINIFile;
  28.     function SearchBuf(Regex: string; const Buffer; Count: integer): integer;
  29.   protected
  30.     procedure LoadStdCGIUserData;
  31.     procedure LoadWinCGIUserData;
  32.     procedure LoadMultiCGIUserData;
  33.     procedure UnpackURLString(S: PChar); virtual;
  34.   public
  35.     constructor Create; virtual;
  36.     destructor Destroy; override;
  37.     procedure DumpWinCGIProfile;
  38.     function GetEnv(Variable: string): string;
  39.     { commented out by Paul Warren}
  40.     { procedure Write(Value: string);}
  41.     { procedure WriteLn(Value: string);}
  42.     property CGIItems: TStringList read FCGIItems;
  43.     property EnvironmentType: TEnvironmentType read FEnvironmentType;
  44.     property FormItems: TStringList read FFormItems;
  45.     property OutputFile: TextFile read FOutputFile write FOutputFile;
  46.     property WinCGIProfile: TINIFile read FWinCGIProfile;
  47.   end;
  48.  
  49. var
  50.   CGI: TCGI;
  51.  
  52. implementation
  53.  
  54. const
  55.   NumCGIVars = 15;
  56.   UpldrDir = 'c:\temp\';
  57.  
  58.   { These are the standard names used by the calling application
  59.     to reference CGI variables.  They generally follow the
  60.     WinCGI names. }
  61.   CGIVarNames: array[0..NumCGIVars - 1] of string[31] =
  62.     ('SERVER SOFTWARE',
  63.      'SERVER NAME',
  64.      'SERVER PORT',
  65.      'CGI VERSION',
  66.      'REQUEST PROTOCOL',
  67.      'REQUEST METHOD',
  68.      'LOGICAL PATH',
  69.      'PHYSICAL PATH',
  70.      'EXECUTABLE PATH',
  71.      'QUERY STRING',
  72.      'REMOTE HOST',
  73.      'REMOTE ADDRESS',
  74.      'REMOTE USER',
  75.      'CONTENT LENGTH',
  76.      'CONTENT TYPE');
  77.  
  78.   { These are the actual variable names used by each protocol. }
  79.   CGIVars: array[0..NumCGIVars - 1, TEnvironmentType] of string[31] =
  80.      { etStdCGI              etWinCGI }
  81.     (
  82.      ('SERVER_SOFTWARE',    'SERVER SOFTWARE'),
  83.      ('SERVER_NAME',        'SERVER NAME'),
  84.      ('SERVER_PORT',        'SERVER PORT'),
  85.      ('GATEWAY_INTERFACE',  'CGI VERSION'),
  86.      ('SERVER_PROTOCOL',    'REQUEST PROTOCOL'),
  87.      ('REQUEST_METHOD',     'REQUEST METHOD'),
  88.      ('PATH_INFO',          'LOGICAL PATH'),
  89.      ('PATH_TRANSLATED',    'PHYSICAL PATH'),
  90.      ('SCRIPT_NAME',        'EXECUTABLE PATH'),
  91.      ('QUERY_STRING',       'QUERY STRING'),
  92.      ('REMOTE_HOST',        'REMOTE HOST'),
  93.      ('REMOTE_ADDR',        'REMOTE ADDRESS'),
  94.      ('REMOTE_USER',        'REMOTE USER'),
  95.      ('CONTENT_LENGTH',     'CONTENT LENGTH'),
  96.      ('CONTENT_TYPE',       'CONTENT TYPE')
  97.      );
  98.  
  99. constructor TCGI.Create;
  100. var
  101.   I: Integer;
  102. begin
  103.   inherited Create;
  104.  
  105.   FCGIItems := TStringList.Create;
  106.   FFormItems := TStringList.Create;
  107.  
  108.   { Detect whether we are standard CGI or WinCGI. }
  109.   if GetEnv('SERVER_NAME') <> '' then
  110.     FEnvironmentType := etStdCGI
  111.   else
  112.   begin
  113.     FEnvironmentType := etWinCGI;
  114.     FWinCGIProfileName := ParamStr(1);
  115.     FWinCGIProfile := TINIFile.Create(FWinCGIProfileName);
  116.   end;
  117.  
  118.   { Assign and open our output file accordingly. }
  119.   case EnvironmentType of
  120.     etStdCGI: AssignFile(OutputFile, '');
  121.     etWinCGI: AssignFile(OutputFile, WinCGIProfile.ReadString('System', 'Output File', ''));
  122.   end;
  123.   Rewrite(OutputFile);
  124.  
  125.   { commented out by Paul Warren}
  126.   { Write standard HTML header for the output page. }
  127.   { WriteLn('Content-type: text/html');}
  128.   { WriteLn('');}
  129.  
  130.   { Load CGI variables and user's form variables. }
  131.   case EnvironmentType of
  132.     etStdCGI: begin
  133.                 for I := 0 to NumCGIVars - 1 do
  134.                   FCGIItems.Values[CGIVarNames[I]] :=
  135.                     GetEnv(CGIVars[I, etStdCGI]);
  136.  
  137.                 if Pos('multipart/form-data', FCGIItems.Values['CONTENT TYPE']) <> 0 then
  138.                 begin
  139.                   FCGIItems.Values['CONTENT BOUNDARY'] :=
  140.                     Copy(FCGIItems.Values['CONTENT TYPE'],
  141.                       Pos('boundary=', FCGIItems.Values['CONTENT TYPE'])+9,
  142.                         Length(FCGIItems.Values['CONTENT TYPE']));
  143.                   LoadMultiCGIUserData;
  144.                 end else
  145.                   LoadStdCGIUserData;
  146.               end;
  147.     etWinCGI: begin
  148.                 for I := 0 to NumCGIVars - 1 do
  149.                   FCGIItems.Values[CGIVarNames[I]] :=
  150.                     WinCGIProfile.ReadString('CGI', CGIVars[I, etWinCGI], '');
  151.  
  152.                 LoadWinCGIUserData;
  153.               end;
  154.   end;
  155. end;
  156.  
  157. destructor TCGI.Destroy;
  158. begin
  159.   CloseFile(OutputFile);
  160.  
  161.   FCGIItems.Free;
  162.   FFormItems.Free;
  163.   FWinCGIProfile.Free;
  164. end;
  165.  
  166. procedure TCGI.DumpWinCGIProfile;
  167. { Writes the contents of the WinCGI profile file to the
  168.   response page. }
  169. var
  170.   FCB: TextFile;
  171.   Rec: string;
  172. begin
  173.   if FWinCGIProfile <> nil then
  174.   begin
  175.     AssignFile(FCB, FWinCGIProfileName);
  176.     Reset(FCB);
  177.     try
  178.       while not Eof(FCB) do
  179.       begin
  180.         ReadLn(FCB, Rec);
  181.         WriteLn(Rec + '<BR>');
  182.       end;
  183.     finally
  184.       CloseFile(FCB);
  185.     end;
  186.   end;
  187. end;
  188.  
  189. function TCGI.GetEnv(Variable: string): string;
  190. { Returns the value iof the given environment variable. }
  191. var
  192.   EnvVariable: array[0..127] of char;
  193.   EnvBuffer: array[0..1023] of char;
  194. begin
  195.   StrPCopy(EnvVariable, Variable);
  196.   Result := '';
  197.   if GetEnvironmentVariable(PChar(Variable), @EnvBuffer, SizeOf(EnvBuffer)) <> 0 then
  198.     Result := StrPas(EnvBuffer);
  199. end;
  200.  
  201. procedure TCGI.LoadStdCGIUserData;
  202. { Reads, parses, and decodes values for the standard CGI form variables. }
  203. var
  204.   ContentLength: LongInt;
  205.   InputFCB: File;
  206.   InputBuffer: PChar;
  207.   RequestMethod: string;
  208.   UserContentBuffer: string;
  209. begin
  210.   RequestMethod := Uppercase(FCGIItems.Values['REQUEST METHOD']);
  211.  
  212.   { If the form action is a POST, then we get form variables from
  213.     the standard input device. }
  214.   if RequestMethod = 'POST' then
  215.   begin
  216.     if FCGIItems.Values['CONTENT TYPE'] <> '' then
  217.     begin
  218.       ContentLength := StrToInt(FCGIItems.Values['CONTENT LENGTH']);
  219.       AssignFile(InputFCB, '');  { standard input }
  220.       Reset(InputFCB, 1);
  221.       try
  222.         InputBuffer := StrAlloc(ContentLength + 1);
  223.         FillChar(InputBuffer^, ContentLength + 1, #0);
  224.         try
  225.           BlockRead(InputFCB, InputBuffer^, ContentLength);
  226.           UnpackURLString(InputBuffer);
  227.         finally
  228.           StrDispose(InputBuffer);
  229.         end;
  230.       finally
  231.         CloseFile(InputFCB);
  232.       end;
  233.     end;
  234.   end
  235.  
  236.   { If the form action is GET, then we get form variables from
  237.     from the QUERY STRING variable. }
  238.   else if RequestMethod = 'GET' then
  239.   begin
  240.     UserContentBuffer := FCGIItems.Values['QUERY STRING'];
  241.     InputBuffer := StrAlloc(Length(UserContentBuffer));
  242.     try
  243.       StrPCopy(InputBuffer, UserContentBuffer);
  244.       UnpackURLString(InputBuffer);
  245.     finally
  246.       StrDispose(InputBuffer);
  247.     end;
  248.   end;
  249. end;
  250.  
  251. // added by Paul Warren
  252. { SearchBuf - execute search on Buffer, modified Boyer-Moore }
  253. function TCGI.SearchBuf(Regex: string; const Buffer; Count: integer): integer;
  254. var
  255.   i, j: integer;
  256.   M, N: integer;
  257.   Skip: array[Char] of integer;
  258.  
  259.   procedure InitSkip;
  260.   var
  261.     Ch: Char;
  262.     i: Integer;
  263.   begin
  264.     for Ch := Low(Char) to High(Char) do Skip[Ch] := M;
  265.     for i := 1 to M do Skip[Regex[i]] := M - i;
  266.   end;
  267.  
  268.   function BufChar(Index: integer): Char;
  269.   begin
  270.      Result := Chr(TByteArray(Buffer)[Index-1]);
  271.   end;
  272.  
  273. begin
  274.   Result := -1; { return -1 if unsuccessful }
  275.   if (Count = 0) or (Regex = '') then exit;
  276.   M := Length(Regex);
  277.   N := Count + 1;
  278.   i := M; j := M;
  279.   InitSkip;
  280.   repeat
  281.     if (BufChar(i) = Regex[j]) then begin
  282.       Dec(i);
  283.       Dec(j);
  284.     end else begin
  285.       if M - j + 1 > Skip[BufChar(i)] then
  286.         i := i + M - j + 1
  287.       else
  288.         i := i + Skip[BufChar(i)];
  289.       j := M;
  290.     end;
  291.   until (j < 1) or (i > N); { found something or reached end }
  292.   if (i > N) then
  293.     Result := -1  { no match - reached end }
  294.   else
  295.     Result := i;  { match begining at i }
  296. end;
  297.  
  298. procedure TCGI.LoadMultiCGIUserData;
  299. { Reads, parses, and decodes values for the standard CGI
  300.   form variables in a multipart form. }
  301. const
  302.   Eom: boolean = false;
  303.   HasContent: boolean = false;
  304. var
  305.   ContentLength: LongInt;
  306.   InputFCB: File;
  307.   RequestMethod: string;
  308.   S: string;
  309.   LabelStr: String;
  310.   ValueStr: String;
  311.   Buffer: array of char;
  312.   AttachStream: TMemoryStream;
  313.  
  314.   function read1ln(var Value: string): integer;
  315.   begin
  316.     Result := SearchBuf(#13#10, Buffer[0], ContentLength)+2;
  317.     SetLength(Value, Result);
  318.     Move(Buffer[0], Value[1], Result);
  319.     Move(Buffer[Result], Buffer[0], Length(Buffer)-Result);
  320.   end;
  321.  
  322.   function readAttachment: integer;
  323.   begin
  324.     Result := SearchBuf(#13#10'--'+CGIItems.Values['CONTENT BOUNDARY'], Buffer[0], ContentLength);
  325.     AttachStream.Write(Buffer[0], Result);
  326.     Move(Buffer[Result], Buffer[0], Length(Buffer)-Result);
  327.   end;
  328.  
  329. begin
  330.   RequestMethod := Uppercase(FCGIItems.Values['REQUEST METHOD']);
  331.   if RequestMethod = 'POST' then
  332.   begin
  333.     if FCGIItems.Values['CONTENT TYPE'] <> '' then
  334.     begin
  335.       ContentLength := StrToInt(FCGIItems.Values['CONTENT LENGTH']);
  336.       AssignFile(InputFCB, '');  { standard input }
  337.       Reset(InputFCB, 1);
  338.       try
  339.         SetLength(Buffer, ContentLength);
  340.         BlockRead(InputFCB, Buffer[0], ContentLength);
  341.         while not Eom do
  342.         begin
  343.           read1ln(S); // read a line
  344.  
  345.           if HasContent then  // if there is content...
  346.           begin
  347.             AttachStream := TMemoryStream.Create;
  348.             try
  349.               // copy to memory stream
  350.               readAttachment;
  351.               // write file to disk
  352.               AttachStream.SaveToFile('c:\temp\'+ChangeFileExt(ExtractFileName(
  353.                 FFormItems.Values['FILENAME']), '')+FloatToStr(
  354.                   TimeStampToMSecs(DateTimeToTimeStamp(Time)))+
  355.                     ExtractFileExt(FFormItems.Values['FILENAME']));
  356.               // save temp file name as form variable
  357.               FFormItems.Values['TEMPFILE'] := 'c:\temp\'+ChangeFileExt(ExtractFileName(
  358.                 FFormItems.Values['FILENAME']), '')+FloatToStr(
  359.                   TimeStampToMSecs(DateTimeToTimeStamp(Time)))+
  360.                     ExtractFileExt(FFormItems.Values['FILENAME']);
  361.             finally
  362.               AttachStream.Free;
  363.             end;
  364.             HasContent := false;
  365.           end;
  366.  
  367.           if S <> #13#10 then
  368.           begin
  369.             while true do
  370.             begin
  371.               if Pos('Content-Disposition', S) <> 0 then
  372.               begin
  373.                 System.Delete(S, 1, Pos('"', S)); // delete to first "
  374.                 LabelStr := System.Copy(S, 1, Pos('"', S)-1); // copy name
  375.                 System.Delete(S, 1, Pos('"', S)); // delete name
  376.                 if Pos('FILENAME', uppercase(S)) <> 0 then
  377.                 begin
  378.                   LabelStr := 'FILENAME';
  379.                   System.Delete(S, 1, Pos('"', S)); // delete to filename
  380.                   ValueStr := System.Copy(S, 1, Pos('"', S)-1); // copy value
  381.                 end;
  382.                 Break;
  383.               end;
  384.               if Pos('Content-Type', S) <> 0 then
  385.               begin
  386.                 LabelStr := 'CONTENT-TYPE';
  387.                 System.Delete(S, 1, Pos(':', S)+1); // delete to :
  388.                 ValueStr := System.Copy(S, 1, Length(S)); // copy name
  389.                 HasContent := true;
  390.                 Break;
  391.               end;
  392.               if Pos(CGIItems.Values['CONTENT BOUNDARY'], S) <> 0 then
  393.               begin
  394.                 // remove first 2 chars
  395.                 System.Delete(S, 1, 2);
  396.                 // check for Eom
  397.                 System.Delete(S, 1, Length(CGIItems.Values['CONTENT BOUNDARY']));
  398.                 if S = '--'#13#10 then Eom := true;
  399.                 HasContent := false; // lower has content flag if got here
  400.                 Break;
  401.               end;
  402.               ValueStr := ValueStr + Copy(S, 1, Pos(#13#10, S)-1); // append to valuestr
  403.               read1ln(S); // read another line
  404.             end;
  405.           end;
  406.           if ValueStr <> '' then
  407.           begin
  408.             FFormItems.Values[LabelStr] := ValueStr;
  409.             LabelStr := '';
  410.             ValueStr := '';
  411.           end;
  412.         end;
  413.       finally
  414.         CloseFile(InputFCB);
  415.       end;
  416.     end;
  417.   end;
  418. end;
  419. // end of addition
  420.  
  421. procedure TCGI.LoadWinCGIUserData;
  422. { Copies values for WinCGI form variables. }
  423. begin
  424.   { All form variables are found in the [Form Literal] section of
  425.     the profile file. }
  426.   WinCGIProfile.ReadSectionValues('Form Literal', TStrings(FFormItems));
  427. end;
  428.  
  429. procedure TCGI.UnpackURLString( S: PChar );
  430. { Parses and decodes a URL-encoded string.  Copies variable values into
  431.   the FFormItems field. }
  432. var
  433.   LabelStr: String;
  434.   ValueStr: String;
  435.   Counter: integer;
  436. begin
  437.   Counter := 0;
  438.   LabelStr := '';
  439.   ValueStr := '';
  440.   while S^ <> #0 do
  441.   begin
  442.     case S^ of
  443.       '+' : ValueStr := ValueStr + ' ';
  444.       '%' : begin
  445.               ValueStr := ValueStr + Chr(StrToInt('$' + (S + 1)^ + (S + 2)^));
  446.               Inc(S, 2);
  447.             end;
  448.       '=' : if LabelStr = '' then
  449.             begin
  450.               LabelStr := ValueStr;
  451.               ValueStr := '';
  452.             end;
  453.       '&' : begin
  454.               while FFormItems.IndexOfName(LabelStr) <> -1 do
  455.               begin
  456.                 LabelStr := LabelStr+IntToStr(Counter);
  457.                 Inc(Counter);
  458.               end;
  459.               FFormItems.Values[LabelStr] := ValueStr;
  460.               ValueStr := '';
  461.               LabelStr := '';
  462.             end;
  463.       else ValueStr := ValueStr + S^;
  464.     end;
  465.     Inc(S);
  466.   end;
  467.  
  468.   if ValueStr <> '' then
  469.     FFormItems.Values[LabelStr] := ValueStr;
  470. end;
  471.  
  472. { Commented out by Paul Warren}
  473.  
  474. (*procedure TCGI.Write(Value: String);
  475. { Standard Write to the output page. }
  476. begin
  477.   System.Write(OutputFile, Value);
  478. end;
  479.  
  480. procedure TCGI.WriteLn(Value: String);
  481. { Standard WriteLn to the output page. }
  482. begin
  483.   System.WriteLn(OutputFile, Value);
  484. end;*)
  485.  
  486. initialization
  487.   CGI := TCGI.Create;
  488. finalization
  489.   CGI.Free;
  490. end.
  491.